#include "loadobj.h"

bool fileExists(char const *path) {
    struct stat my_stat;
    return (stat(path, &my_stat) == 0);
}

bool isdir(char const *path) {
    struct stat my_stat;
    stat(path, &my_stat);
    return (my_stat.st_mode & S_IFMT) == S_IFDIR;
}

bool isfile(char const *path) {
    struct stat my_stat;
    stat(path, &my_stat);
    return (my_stat.st_mode & S_IFMT) == S_IFREG;
}

string dir_(char const *path) {
    string dstpath;
    string path_(path);
    if (path_.rfind("/") == string::npos && path_.rfind("\\") == string::npos) {                    
        dstpath = "./";
    } else {
        if (path_.rfind("/") != string::npos) {
            dstpath = path_.substr(0, path_.rfind("/")) + "/";
        } else if (path_.rfind("\\") != string::npos) {
            dstpath = path_.substr(0, path_.rfind("\\")) + "/";
        }
    }
    return dstpath;
}

vector<string> loadContent(char const *path) {
    vector<string> result;
    if (!isfile(path)) return result;
    ifstream is(path, ios::in);
    if(!is.is_open()) {
        printf("Failed to open: %s\n", path);
        return result;
    }
    while (!is.eof()) {
        char buf[1024] = {0};
        is.getline(buf, sizeof(buf));
        if (strlen(buf) <= 0 || strncmp(buf, "#", 1) == 0) continue;
        result.push_back(string(buf));
    }
    return result;
}

vector<vector<string>> processContents(vector<string> contents, string flag) {
    vector<vector<string>> contents_;
    vector<int> indices;
    for (int i = 0, l = contents.size(); i < l; ++i) {
        if (strncmp(contents[i].c_str(), flag.c_str(), flag.size()) == 0) {
            indices.push_back(i);
        }
    }
    for (int i = 0, l = indices.size(); i < l; ++i) {
        int begin = indices[i], end;
        if ( i == l - 1) {
            end = contents.size() - 1;
        } else {
            end = indices[i + 1] - 1;
        }
        vector<string> content;
        for (int im = begin; im <= end; ++im) {
            content.push_back(contents[im]);
        }
        contents_.push_back(content);
    }
    return contents_;
}
vector<vector<string>> loadMeshContents(char const *path) {
    vector<vector<string>> meshContents;
    if (string(path).rfind(".obj") == string::npos) {
        return meshContents;
    }
    vector<string> contents = loadContent(path);
    return processContents(contents, "o ");
}

vector<vector<string>> loadMtlContents(char const *path) {
    vector<vector<string>> mtlContents;
    vector<string> contents;
    string path_(path);
    if (path_.rfind(".mtl") == string::npos) {
        if (path_.rfind(".obj") == string::npos) {
            return mtlContents;
        } else {
            contents = loadContent(path);
            string mtllib;
            for (int i = 0, l = contents.size(); i < l; ++i) {
                if (strncmp(contents[i].c_str(), "mtllib ", 7) == 0) {
                    mtllib = dir_(path) + string(contents[i].c_str()+7);
                    break;
                }
            }
            if (mtllib.size() <= 0) {
                return mtlContents;
            }
            contents = loadContent(mtllib.c_str());
        }
    } else {
        contents = loadContent(path);
    }
    return processContents(contents, "newmtl ");
}

vector<Mesh> processModel(char const *path) {
    vector<vector<string>> meshContents = loadMeshContents(path);
    vector<vector<string>> mtlContents = loadMtlContents(path);

    vector<Mesh> meshes;
    vector<Material> mtls;
    for (int i = 0, l = mtlContents.size(); i < l; ++i) {
        Material mat = {"", "", ""};
        vector<string> content = mtlContents[i];
        for (int im = 0, lm = content.size(); im < lm; ++im) {
            char const * buf = content[im].c_str();
            if (strncmp(buf, "newmtl ", 7) == 0) {
                sscanf(buf, "newmtl %s", mat.newmtl);
            }
            if (strncmp(buf, "map_Kd ", 7) == 0) {
                sscanf(buf, "map_Kd %s", mat.map_Kd);
            }
            if (strncmp(buf, "map_Bump ", 9) == 0) {
                sscanf(buf, "map_Bump %s", mat.map_Bump);
            }
        }
        mtls.push_back(mat);
    }
    vector<Texture> loadTextures;
    for (int i = 0, l = meshContents.size(); i < l; ++i) {
        vector<string> content = meshContents[i];
        vector<glm::vec3> positions;
        vector<glm::vec3> normals;
        vector<glm::vec2> texCoords;
        vector<Face> faces;
        vector<Texture> textures;
        for (int im = 0, lm = content.size(); im < lm; ++im) {
            char const * buf = content[im].c_str();
            if (strncmp(buf, "v ", 2) == 0) {
                glm::vec3 position;
                sscanf(buf, "v %f %f %f", &position.x, &position.y, &position.z);
                positions.push_back(position);
            }
            if (strncmp(buf, "vn ", 3) == 0) {
                glm::vec3 normal;
                sscanf(buf, "vn %f %f %f", &normal.x, &normal.y, &normal.z);
                normals.push_back(normal);
            }
            if (strncmp(buf, "vt ", 3) == 0) {
                glm::vec2 texCoord;
                sscanf(buf, "vt %f %f", &texCoord.x, &texCoord.y);
                texCoords.push_back(texCoord);
            }
            if (strncmp(buf, "f ", 2) == 0) {
                Face face;
                sscanf(buf, "f %d/%d/%d %d/%d/%d %d/%d/%d", &face.v1[0], &face.v1[1], &face.v1[2], &face.v2[0], &face.v2[1], &face.v2[2], &face.v3[0], &face.v3[1], &face.v3[2]);
                faces.push_back(face);
            }
            if (strncmp(buf, "usemtl ", 7) == 0) {
                // printf("buf: %s\n", buf);
                char usemtl[128];
                sscanf(buf, "usemtl %s", usemtl);
                for (int im_ = 0, lm_ = mtls.size(); im_ < lm_; ++im_) {
                    if (strcmp(mtls[im_].newmtl, usemtl) == 0) {
                        char texpath[1024] = {0};
                        if (strlen(mtls[im_].map_Kd) > 0) {
                            bool skip = false;
                            for (int it = 0, lt = textures.size(); it < lt; ++it) {
                                if (strcmp(textures[it].path, mtls[im_].map_Kd) == 0) {
                                    Texture texture = textures[it];
                                    strcpy(texture.type, "texture_diffuse");
                                    textures.push_back(texture);
                                    skip = true;
                                    break;
                                }
                            }
                            if (!skip) {
                                sprintf(texpath, "%s%s", dir_(path).c_str(), mtls[im_].map_Kd);
                                // printf("mtls[im_].map_Kd: %s\n", texpath);
                                unsigned int textureid;
                                loadimg(&textureid, texpath);
                                Texture texture = {textureid, "texture_diffuse", ""};
                                strcpy(texture.path, mtls[im_].map_Kd);
                                textures.push_back(texture);
                            }
                        }
                        if (strlen(mtls[im_].map_Bump) > 0) {
                            bool skip = false;
                            for (int it = 0, lt = textures.size(); it < lt; ++it) {
                                if (strcmp(textures[it].path, mtls[im_].map_Bump) == 0) {
                                    Texture texture = textures[it];
                                    strcpy(texture.type, "texture_specular");
                                    textures.push_back(texture);
                                    skip = true;
                                    break;
                                }
                            }
                            if (!skip) {
                                sprintf(texpath, "%s%s", dir_(path).c_str(), mtls[im_].map_Bump);
                                // printf("mtls[im_].map_Bump: %s\n", texpath);
                                unsigned int textureid;
                                loadimg(&textureid, texpath);
                                Texture texture = {textureid, "texture_specular", ""};
                                strcpy(texture.path, mtls[im_].map_Bump);
                                textures.push_back(texture);
                            }
                        }
                    }
                }
            }
        }
        int i_ = 0;
        vector<Vertex> vertices;
        vector<unsigned int> indices;
        for (int i = 0, l = faces.size(); i < l; ++i) {
            Vertex vertex1 = {positions[faces[i].v1[0] - 1], normals[faces[i].v1[2] - 1], texCoords[faces[i].v1[1] - 1]};
            Vertex vertex2 = {positions[faces[i].v2[0] - 1], normals[faces[i].v2[2] - 1], texCoords[faces[i].v2[1] - 1]};
            Vertex vertex3 = {positions[faces[i].v3[0] - 1], normals[faces[i].v3[2] - 1], texCoords[faces[i].v3[1] - 1]};
            
            vertices.push_back(vertex1);
            vertices.push_back(vertex2);
            vertices.push_back(vertex3);
            
            indices.push_back(i_++);
            indices.push_back(i_++);
            indices.push_back(i_++);
        }
        Mesh mesh(vertices, indices, textures);
        meshes.push_back(mesh);
    }
    return meshes;
}

void LoadModel::processNode(aiNode *node, const aiScene *scene) {
    for(unsigned int i = 0; i < node->mNumMeshes; i++)
    {
        aiMesh *mesh = scene->mMeshes[node->mMeshes[i]];

        vector<Vertex> vertices;
        vector<unsigned int> indexs;
        vector<Texture> textures;

        for(int i = 0, l = mesh->mNumVertices; i < l; i++){
            Vertex vertex;
            
            glm::vec3 position = glm::vec3(1.0f);
            position.x = mesh->mVertices[i].x;
            position.y = mesh->mVertices[i].y;
            position.z = mesh->mVertices[i].z;
            
            glm::vec3 normal = glm::vec3(1.0f);
            normal.x = mesh->mNormals[i].x;
            normal.y = mesh->mNormals[i].y;
            normal.z = mesh->mNormals[i].z;
            
            glm::vec2 texCoord = glm::vec2(1.0f);
            if(mesh->mTextureCoords[0]){
                texCoord.x = mesh->mTextureCoords[0][i].x;
                texCoord.y = mesh->mTextureCoords[0][i].y;
            }
            
            vertex.position = position;
            vertex.normal = normal;
            vertex.texCoord = texCoord;
            
            vertices.push_back(vertex);
        }

        for(int i = 0, l = mesh->mNumFaces; i < l; i++){
            aiFace face =mesh->mFaces[i];
            for(int j = 0, k = face.mNumIndices; j < k; j++){
                indexs.push_back(face.mIndices[j]);
            }
        }
        // printf("mesh->mMaterialIndex: %d\n", mesh->mMaterialIndex);
        if(mesh->mMaterialIndex > 0){
            aiMaterial* mat = scene->mMaterials[mesh->mMaterialIndex];
            vector<Texture> diffuseMap = processTextures(mat, aiTextureType_DIFFUSE, "texture_diffuse");
            textures.insert(textures.begin(), diffuseMap.begin(), diffuseMap.end());
            vector<Texture> specularMap = processTextures(mat, aiTextureType_SPECULAR, "texture_specular");
            textures.insert(textures.begin(), specularMap.begin(), specularMap.end());
        }
        meshes.push_back(Mesh(vertices, indexs, textures));
        
        // for (int i_ = 0, l_ = textures.size(); i_ < l_; ++i_) {
        //     printf("size: %d, (%d, %s, %s)\n", l_, textures[i_].id, textures[i_].type, textures[i_].path);
        // }
    }
    for(int i = 0, l = node->mNumChildren; i < l; i++){
		processNode(node->mChildren[i], scene);
	}
}

vector<Texture> LoadModel::processTextures(aiMaterial* mat, aiTextureType type, string typeName) {
    vector<Texture> textures;
    for(int i = 0, l = mat->GetTextureCount(type); i < l; i++){
        aiString str;
        mat->GetTexture(type, i, &str);
        bool skip = false;
        for (int it = 0, lt = textureLoaded.size(); it < lt; ++it) {
            if (strcmp(textureLoaded[it].path, str.C_Str()) == 0) {
                Texture texture = textureLoaded[it];
                strcpy(texture.type, typeName.c_str());
                textures.push_back(texture);
                skip = true;
                break;
            }
        }
        if (!skip) {
            unsigned int textureid;
            loadimg(&textureid, (dirctory + "/" + string(str.C_Str())).c_str());
            Texture texture = {textureid};
            strcpy(texture.type, typeName.c_str());
            strcpy(texture.path, str.C_Str());
            textures.push_back(texture);
            textureLoaded.push_back(texture);
        }
    }
    return textures;
}
